In [1]:
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

import plotly.graph_objs as go
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
from plotly import tools
import plotly.express as px
import plotly.offline as py
import scipy.stats as stats
In [2]:
telcom = pd.read_csv('WA_Fn-UseC_-Telco-Customer-Churn.csv', sep=',', encoding='latin1')
In [3]:
#Data Manipulation

#Replacing spaces with null values in total charges column
telcom['TotalCharges'] = telcom["TotalCharges"].replace(" ",np.nan)

#Dropping null values from total charges column which contain .15% missing data 
telcom = telcom[telcom["TotalCharges"].notnull()]
telcom = telcom.reset_index()[telcom.columns]

#convert to float type
telcom["TotalCharges"] = telcom["TotalCharges"].astype(float)

#replace 'No internet service' to No for the following columns
replace_cols = [ 'OnlineSecurity', 'OnlineBackup', 'DeviceProtection',
                'TechSupport','StreamingTV', 'StreamingMovies']
for i in replace_cols : 
    telcom[i]  = telcom[i].replace({'No internet service' : 'No'})
    
#replace values
telcom["SeniorCitizen"] = telcom["SeniorCitizen"].replace({1:"Yes",0:"No"})

#Tenure to categorical column
def tenure_lab(telcom) :
    if telcom["tenure"] <= 12 :
        return "0-12"
    elif (telcom["tenure"] > 12) & (telcom["tenure"] <= 24 ):
        return "12-24"
    elif (telcom["tenure"] > 24) & (telcom["tenure"] <= 48) :
        return "24-48"
    elif (telcom["tenure"] > 48) & (telcom["tenure"] <= 60) :
        return "48-60"
    elif telcom["tenure"] > 60 :
        return "gt_60"
telcom["tenure_group"] = telcom.apply(lambda telcom:tenure_lab(telcom),
                                      axis = 1)

#Separating churn and non churn customers
churn     = telcom[telcom["Churn"] == "Yes"]
not_churn = telcom[telcom["Churn"] == "No"]

#Separating catagorical and numerical columns
Id_col     = ['customerID']
target_col = ["Churn"]
cat_cols   = telcom.nunique()[telcom.nunique() < 6].keys().tolist()
cat_cols   = [x for x in cat_cols if x not in target_col]
num_cols   = [x for x in telcom.columns if x not in cat_cols + target_col + Id_col]
In [4]:
telcom.head()
Out[4]:
customerID gender SeniorCitizen Partner Dependents tenure PhoneService MultipleLines InternetService OnlineSecurity ... TechSupport StreamingTV StreamingMovies Contract PaperlessBilling PaymentMethod MonthlyCharges TotalCharges Churn tenure_group
0 7590-VHVEG Female No Yes No 1 No No phone service DSL No ... No No No Month-to-month Yes Electronic check 29.85 29.85 No 0-12
1 5575-GNVDE Male No No No 34 Yes No DSL Yes ... No No No One year No Mailed check 56.95 1889.50 No 24-48
2 3668-QPYBK Male No No No 2 Yes No DSL Yes ... No No No Month-to-month Yes Mailed check 53.85 108.15 Yes 0-12
3 7795-CFOCW Male No No No 45 No No phone service DSL Yes ... Yes No No One year No Bank transfer (automatic) 42.30 1840.75 No 24-48
4 9237-HQITU Female No No No 2 Yes No Fiber optic No ... No No No Month-to-month Yes Electronic check 70.70 151.65 Yes 0-12

5 rows × 22 columns

Идея

Я выбрала базу данных клиентов Оператора универсальных услуг связи. В этой базе так же отмечено, ушел ли клиент от этого оператора или остался.

Идеей моего проекта было понять, почему клиенты уходят и как это предотвратить, как исправить ситуацию.

Обзор

Сначала я решила посмотреть, с какими данными я работаю и как соотносятся разные величины для оставшихся и ушедших клиентов.

Первым параметром было Количество ушедших и оставшихся клиентов.

In [5]:
#labels

lab = telcom["Churn"].value_counts().keys().tolist()
lab = ["Остались","Ушло" ]
#values
val = telcom["Churn"].value_counts().values.tolist()

trace = go.Pie(labels = lab ,
               values = val ,
               marker = dict(colors =  [px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                             line = dict(color = "white",
                                         width =  1.3)
                            ),
               rotation = 94,
               hoverinfo = "label+value+text"
              )
layout = go.Layout(dict(title = "Количество ушедших и оставшихся клиентов",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                       )
                  )

data = [trace]
fig = go.Figure(data = data,layout = layout)
py.iplot(fig)

Большее количество клиентов остаются в кампании, но 26.6 процентов все-таки уходят

Далее я разделила всех клиентов на оставшихся и ушедших и смотрела по разным категориям.

In [6]:
#function  for pie plot for customer attrition types
def plot_pie(column) :
    
    trace1 = go.Pie(values  = churn[column].value_counts().values.tolist(),
                    labels  = churn[column].value_counts().keys().tolist(),
                    hoverinfo = "label+percent+name",
                    domain  = dict(x = [0,.48]),
                    name    = "Churn Customers",
                    marker  = dict(colors = [px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]
                                             ,px.colors.sequential.Sunsetdark[2]
                                            ,px.colors.sequential.Sunsetdark[1]],
                        line = dict(width = 2,
                                               color = "rgb(243,243,243)")
                                  ),
                    hole    = .6
                   )
    trace2 = go.Pie(values  = not_churn[column].value_counts().values.tolist(),
                    labels  = not_churn[column].value_counts().keys().tolist(),
                    hoverinfo = "label+percent+name",
                    marker  = dict(
                        line = dict(width = 2,
                                               color = "rgb(243,243,243)")
                                  ),
                    domain  = dict(x = [.52,1]),
                    hole    = .6,
                    name    = "Non churn customers" 
                   )
    if column == "gender":
        col_name = "Гендерное распределение клиентов "
    elif column == "SeniorCitizen":
        col_name = " Является ли клиент пожилым или нет"
    elif column == "Partner":
        col_name = " Есть ли у клиента партнер или нет"
    elif column == "Dependents":
        col_name = " Есть ли у клиента иждевенцы или нет"
    elif column == "PhoneService":
        col_name = " Есть ли у клиента телефон или нет"
    elif column == "MultipleLines":
        col_name = " Есть ли у клиента несколько линий или нет"
    elif column == "InternetService":
        col_name = "Интернет-провайдер клиента"
    elif column == "OnlineSecurity":
        col_name = " Есть ли у клиента онлайн-безопасность или нет"
    elif column == "OnlineBackup":
        col_name = "Есть ли у клиента онлайн-резервное копирование или нет"
    elif column == "DeviceProtection":
        col_name = " Есть ли у клиента защита устройства или нет"
    elif column == "TechSupport":
        col_name = " Есть ли у клиента техническая поддержка или нет"
    elif column == "StreamingTV":
        col_name = "Есть ли у клиента потоковое телевидение или нет"
    elif column == "StreamingMovies":
        col_name = " Есть ли у клиента потоковое кино или нет"
    elif column == "Contract":
        col_name = "Срок договора заказчика"
    elif column == "PaperlessBilling":
        col_name = "Есть ли у клиента безбумажный счет или нет"
    elif column == "PaymentMethod":
        col_name = " Способ оплаты клиента"
    elif column == "tenure_group":
        col_name = " Количество месяцев, в течение которых клиент находился в компании"
    #elif column == "churn_rate":
   #     col_name = "churn_rate"
 

    layout = go.Layout(dict(title = col_name,
                            plot_bgcolor  = "rgb(243,243,243)",
                            paper_bgcolor = "rgb(243,243,243)",
                            annotations = [dict(text = "Ушедшие клиенты",
                                                font = dict(size = 17),
                                                showarrow = False,
                                                x = .13, y = .5),
                                           dict(text = "Оставшиеся клиенты",
                                                font = dict(size = 17),
                                                showarrow = False,
                                                x = .88,y = .5
                                               )
                                          ]
                           )
                      )
    data = [trace1,trace2]
    fig  = go.Figure(data = data,layout = layout)
    py.iplot(fig)




#for all categorical columns plot pie
for i in cat_cols[0:-1] :
    plot_pie(i)

Гендерное распределение клиентов:

  • Получаем одинаковое распределение мужчин и женщин среди оставшихся и ушедших клинтов

Является ли клиент пожилым или нет

  • Среди ушедших клиетов больше процент (примерно в 2 раза) пожилых людей ( четверть ушедших людей пожилые) (возможно, что это связано со смертью клиентов)
  • Может влиять на уход

Есть ли у клиента партнер или нет

  • Среиди ушедших клиентов, людей, у которых нет партнера больше (разница в 30%)
  • Из оставшихся клиентов больше тех, у которых есть партнер, но не на много, доли очень близки.
  • (Возможно, что это связано с тем, что людям есть кому звонить - своей второй половинке)
  • Может влиять на уход

Есть ли у клиента иждевенцы или нет

  • В обоих группах клиентов, людей, у которых нет иждевенцев больше.
  • В группе оставшихся клиентов процент людей с иждевенцами больше в 2 раза
  • На уход не влияет

Есть ли у клиента телефонный сервис или нет

  • В обоих группах у примерно 9-10 процентов клиентов нет телефонного сервиса
  • Не влияет на уход

Есть ли у клиента несколько линий или нет

  • У ушедших клиентов одинаковое распределение людей с несколькими линиями и без них
  • у оставшихся клиентов людей, у которых нет несколько линий немного (на 8 процентов) больше
  • Не влияет на отток

Интернет-провайдер клиента

  • Ушедшие клиенты больше пользовались оптоволоконным соединением
  • только у 6% клиентов не было интернет-соединения
  • Оставшиеся клиенты с одинаковым количеством процентов лиюо не имеют интернет соединение, либо пользуются оптоаолоконным, либо DSL интернетом.
  • Интересно, что клиенты с DSL (более медленным подключением) менее подвержены оттоку.

Есть ли у клиента онлайн-безопасность или нет

  • У ушедших клиентов в основном (84 %) не было онлайн безопасности.
  • У оставшихся клиентов только 33% имели онлайн безопасность.
  • Не влияет на уход

Есть ли у клиента онлайн-резервное копирование или нет

  • Это так же не самая популярная услуга, как среди оставшихся клиентов, так и среди ушидших
  • Среди ушедших 28% пользовались
  • Среди оставшихся 33% пользовались
  • Не влияет на уход

Есть ли у клиента защита устройства или нет

  • Оставшиеся клиенты больше пользовались услугой (36 %), чем ушедшие клиенты (29 %)

Есть ли у клиента техническая поддержка или нет

  • Только 16 % клиентов пользовались тех. поддержкой.
  • Возможно из-за этого люди могли не решить свои проблемы и выйти из кампании
  • у 33 % оставшихся клиентов была тех. поддержка
  • Может влиять на уход

Есть ли у клиента потоковое телевидение и фильмы или нет

  • Оставшиеся клиенты меньше пользовались телевидением и смотрели фидьмы (37%), чем ушедшие клиенты (44%)
  • Может их не устраивали сервисы и поэтому они уходили
  • Может влиять на уход

Срок договора заказчика

  • В основном, ушедшие клиенты заключали договоры на месяц (от месяца к месяцу)
  • Очень маленький процент ушедших клиентов (заключали договор на год (8.8 %) и на 2 года (2.5 %)
  • Оставщиеся клиенты в основном заключают договоры на месяц (43 %) и так же не малую долю составляет 2 других срока: 32% - 2 года, 25% - год.
  • Можно посмотреть на сколько люди оставались после заключения каждого вида договора

Есть ли у клиента безбумажный счет или нет

  • У ушедших клиентов только у 25 % есть безбумажный счет
  • У оставшихся клиентов 47% есть безбумажный счет
  • Может быть причиной оттока

Способ оплаты клиента

  • У ушедших клиентов наиболее популярным способом оплаты по электронному чеку
  • остальные способы оплаты распределились примерно одинаково ( 16,5% - чек по почте, 13.8% - автоматический баковский переод, 12.4% - автоматическое списание с бвнковской карты)
  • У оставшихся клиентов все виды оплат распределились одинаковым образом (примерно 25%)
  • Клиетам может не нравится сервис с оплтой по электоронному чеку, хначит они уходят из кампании
  • Может влиять на отток клиентов
In [7]:
cols = ["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies","PhoneService" ]

telcom1 = pd.melt(telcom[telcom["InternetService"] != "No"][cols]).rename({'value': 'Пользуются услугой'}, axis=1).rename({'variable': 'Услуга'}, axis=1)
fig = px.histogram(telcom1, x="Услуга", color="Пользуются услугой",
                   color_discrete_sequence=[px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                  title = "Колличество людей, которые пользуются каждой из услуг",
                  labels = ["Количество", "Услуга"])
fig.show()

Наиболее популярными услугами оказались телефон, просмотр телевизора и фильмов

In [8]:
cols = ["OnlineSecurity", "OnlineBackup", "DeviceProtection", "TechSupport", "StreamingTV", "StreamingMovies","PhoneService" ]
telcom1 = pd.melt(telcom[telcom["InternetService"] == "No"][cols]).rename({'value': 'Пользуются услугой'}, axis=1).rename({'variable': 'Услуга'}, axis=1)
fig = px.histogram(telcom1, x="Услуга", color="Пользуются услугой",
                   color_discrete_sequence=[px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                  title = "Колличество людей, которые пользуются каждой из услуг, если нет интернета",
                  labels = ["Количество", "Услуга"])
fig.show()

Надо обратить внимание на то, что если у человека не подключен интернет, то он не пользуется остальными услугами либо без интернета он не может ими пользоваться.

Пожилые клиенты

In [9]:
lab =telcom.groupby('SeniorCitizen')["SeniorCitizen"].value_counts().keys().tolist()
lab = ["Молодые","Пожилые" ]
#values
val = telcom.groupby('SeniorCitizen')["SeniorCitizen"].value_counts().values.tolist()

trace = go.Pie(labels = lab ,
               values = val ,
               marker = dict(colors =  [px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                             line = dict(color = "white",
                                         width =  1.3)
                            ),
               #rotation = 90,
               hoverinfo = "label+value+text",
              )
layout = go.Layout(dict(title = "Количество  клиентов пожилого возраста",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                       )
                  )

data = [trace]
fig = go.Figure(data = data,layout = layout)
py.iplot(fig)

Пожилых людей не так много, но

In [10]:
lab = telcom[ (telcom['SeniorCitizen']=='Yes')].groupby('Churn')["Churn"].value_counts().keys().tolist()
lab = ["Остались","Ушло" ]
#values
val = telcom[ (telcom['SeniorCitizen']=='Yes')].groupby('Churn')["Churn"].value_counts().values.tolist()

trace = go.Pie(labels = lab ,
               values = val ,
               marker = dict(colors =  [px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                             line = dict(color = "white",
                                         width =  1.3)
                            ),
               #rotation = 90,
               hoverinfo = "label+value+text"
              )
layout = go.Layout(dict(title = "Количество ушедших и оставшихся клиентов пожилого возраста",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                       )
                  )

data = [trace]
fig = go.Figure(data = data,layout = layout)
py.iplot(fig)

Большая доля пенсионеров уходит

Посмотрим, почему они могут уходить.

Во-первых, посмотрим, какими услугами они пользуются:

In [11]:
df1 = telcom[(telcom.InternetService != "No") &(telcom['SeniorCitizen']=='Yes')]
df2 = pd.melt(df1[cols]).rename({'value': 'Пользуются услугой'}, axis=1).rename({'variable': 'Услуга'}, axis=1)
fig = px.histogram(df2, x="Услуга", color="Пользуются услугой",
                   color_discrete_sequence=[px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                  title = "Колличество пожилых людей в зависимости от услуг",
                  labels = ["Количество", "Услуга"])
fig.show()
  • Видно, что большинство пожилых людей пользуются телефоном.
  • Оказалось, что самыми популярными сервисами оказались телевидение и просмотр фильмов.
In [12]:
df1 = telcom[(telcom.InternetService != "No") &(telcom['SeniorCitizen']=='Yes')&(telcom['Churn']=='Yes')]
df2 = pd.melt(df1[cols]).rename({'value': 'Пользуются услугой'}, axis=1).rename({'variable': 'Услуга'}, axis=1)
fig = px.histogram(df2, x="Услуга", color="Пользуются услугой",
                   color_discrete_sequence=[px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                  title = "Колличество пожилых людей, которые ушли от оператора в зависимости от услуг",
                  labels = ["Количество", "Услуга"])
fig.show()

Оказалось, что большинство пожилых людей, которые ушли из кампании, использовали телевидение и смотрели фильмы.

Вывод

Можно сделать вывод о том, что люди пожилого возраста уходят из кампании, потому что они в основном смотрят телевизор и фильм и интернет им не нужен и они не хотят тратить деньги на лишние услуги.

Для решения проблемы можно:

  • давать дополнительные скидки для пожилых людей.
  • попытаться сделать сервис для просмотра фильмов и телевизилнных передач без необходимого подключения к интернету.

Cпособ оплаты

In [13]:
telcom.groupby(['PaymentMethod','Churn'])['PaymentMethod'].count()
Out[13]:
PaymentMethod              Churn
Bank transfer (automatic)  No       1284
                           Yes       258
Credit card (automatic)    No       1289
                           Yes       232
Electronic check           No       1294
                           Yes      1071
Mailed check               No       1296
                           Yes       308
Name: PaymentMethod, dtype: int64
In [14]:
#cusomer attrition in tenure groups
tg_ch2  =  churn['PaymentMethod'].value_counts().reset_index()
tg_ch2.columns  = ["PaymentMethod","count"]
tg_nch2 =  not_churn['PaymentMethod'].value_counts().reset_index()
tg_nch2.columns = ["PaymentMethod","count"]

#bar - churn
trace1 = go.Bar(x = tg_ch2["PaymentMethod"]  , y = tg_ch2["count"],
                name = "Ушедшие клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[3],line = dict(width = .5)),
                opacity = .9)

#bar - not churn
trace2 = go.Bar(x = tg_nch2["PaymentMethod"] , y = tg_nch2["count"],
                name = "Оставшиеся клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[0],line = dict(width = .5)),
                opacity = .9)

layout = go.Layout(dict(title = "Отток пользователей в зависимости от способа оплаты",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                        xaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Вид оплаты",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                        yaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Количество",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                       )
                  )
data = [trace1,trace2]
fig  = go.Figure(data=data,layout=layout)
py.iplot(fig)

У оставшихся клиентов все виды оплат распределились одинаковым образом (примерно 25%)

Сервис с оплатой по электронному чеку имеет наибольший отток. Клиетам может не нравится сервис с опатой по электоронному чеку, хначит они уходят из кампании

Вывод

Чтобы избавиться от проблемы необходимо улучшить сервис с оплатой по электронному чеку.

Период пребывания в кампании и расходы

In [15]:
correlation = telcom.corr()
#tick labels
matrix_cols = ["Всего платежей","Ежемесячные платежи","Cрок пребывания"]#correlation.columns.tolist()
#convert to array
corr_array  = np.array(correlation)

#Plotting
trace = go.Heatmap(z = corr_array,
                   x = matrix_cols,
                   y = matrix_cols,
                   colorscale = "sunsetdark",
                   colorbar   = dict(title = "Коэффициент корреляции Пирсона",
                                     titleside = "right"
                                    ) ,
                  )

layout = go.Layout(dict(title = "Матрица корреляции для переменных",
                        autosize = False,
                        height  = 720,
                        width   = 800,
                        margin  = dict(r = 0 ,l = 210,
                                       t = 25,b = 210,
                                      ),
                        yaxis   = dict(tickfont = dict(size = 15)),
                        xaxis   = dict(tickfont = dict(size = 15))
                       )
                  )

data = [trace]
fig = go.Figure(data=data,layout=layout)
py.iplot(fig)

Зависят друг от друга: Срок пребывания и Общая сумма платежей

Не зависят: ежемесячный платеж и общая сумма платежа

Чтобы лучше понять, как соотносятся величины, потроим точечную диаграмму.

In [16]:
#функция матрицы рассеяния для числовых столбцов в данных
def scatter_matrix(df)  :
    
    df  = df.sort_values(by = "Churn" ,ascending = True)
    classes = df["Churn"].unique().tolist() # разделили на 2 группы ['No', 'Yes']
    classes
    
    class_code  = {classes[k] : k for k in range(2)} #присвоили группе код {'No': 0, 'Yes': 1}
    class_code

    color_vals = [class_code[cl] for cl in df["Churn"]] # переделали столбец на цифры
    color_vals

    pl_colorscale = [px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]]

    pl_colorscale

    text = [df.loc[k,"Churn"] for k in range(len(df))]
    text

    trace = go.Splom(dimensions = [dict(label  = "Срок пребывания",
                                       values = df["tenure"]),
                                  dict(label  = 'Ежемесячные платежи',
                                       values = df['MonthlyCharges']),
                                  dict(label  = 'Всего платежей',
                                       values = df['TotalCharges'])],
                     text = text,
                     marker = dict(color = color_vals,
                                   colorscale = pl_colorscale,
                                   size = 3,
                                   showscale = False,
                                   line = dict(width = .1,
                                               color='rgb(230,230,230)'
                                              )
                                  )
                    )
    axis = dict(showline  = True,
                zeroline  = False,
                gridcolor = "#fff",
                ticklen   = 4
               )
    
    layout = go.Layout(dict(title  = 
                            "Матрица точечной диаграммы для числовых столбцов для оттока клиентов",
                            autosize = False,
                            height = 800,
                            width  = 800,
                            dragmode = "select",
                            hovermode = "closest",
                            plot_bgcolor  = 'rgba(240,240,240, 0.95)',
                            xaxis1 = dict(axis),
                            yaxis1 = dict(axis),
                            xaxis2 = dict(axis),
                            yaxis2 = dict(axis),
                            xaxis3 = dict(axis),
                            yaxis3 = dict(axis),
                           )
                      )
    data   = [trace]
    fig = go.Figure(data = data,layout = layout )
    py.iplot(fig)

    
    #scatter plot matrix
scatter_matrix(telcom)

Зависимость всей суммы платежей от срока пребывания

  • Как видно из графика, чем больше ушедший человек провел врмени в кампании, тем больше он заплатил.
  • Некоторые люди, которые остались в кампании заплатили не так много
  • Может влиять на отток клиентов: плата повышается, становится не выгодно находить в кампании

Зависимость всей суммы платежей от ежемесечного платежа

  • Слабая зависимость: чем больше у человека ежемесячный платеж, тем больше он денег отдал всего

Зависимость ежемесячного платежа от срока пребывания

  • Никак не зависит
  • Люди, которые покинули кампанию, платили больше
In [17]:
#cusomer attrition in tenure groups
tg_ch  =  churn["tenure_group"].value_counts().reset_index()
tg_ch.columns  = ["tenure_group","count"]
tg_nch =  not_churn["tenure_group"].value_counts().reset_index()
tg_nch.columns = ["tenure_group","count"]

#bar - churn
trace1 = go.Bar(x = tg_ch["tenure_group"]  , y = tg_ch["count"],
                name = "Ушедшие клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[3],line = dict(width = .5)),
                opacity = .9)

#bar - not churn
trace2 = go.Bar(x = tg_nch["tenure_group"] , y = tg_nch["count"],
                name = "Оставшиеся клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[0],line = dict(width = .5)),
                opacity = .9)

layout = go.Layout(dict(title = "Отток пользователей в разрезе количества месяцев",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                        xaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Срок пребывания, месяцы",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                        yaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Количество",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                       )
                  )
data = [trace1,trace2]
fig  = go.Figure(data=data,layout=layout)
py.iplot(fig)

Отток пользователей в разрезе количества месяцев

  • Больше всего людей уходят из кампании в первые 12 месяцев
In [18]:
avg_tgc = telcom.groupby(["tenure_group","Churn"])[["MonthlyCharges",
                                                    "TotalCharges"]].mean().reset_index()

#function for tracing 
def mean_charges(column,aggregate) :
    if aggregate == "Yes":
        color= px.colors.sequential.Sunsetdark[3]
        name1="Ушли"
    else:
        color= px.colors.sequential.Sunsetdark[0]
        name1="Остались"
    
    tracer = go.Bar(x = avg_tgc[avg_tgc["Churn"] == aggregate]["tenure_group"],
                    y = avg_tgc[avg_tgc["Churn"] == aggregate][column],
                    name = name1,marker = dict(color =  color,line = dict(width = 1)),
                    text = "Churn"
                   )
    return tracer

#function for layout
def layout_plot(title,xaxis_lab,yaxis_lab) :
    layout = go.Layout(dict(title = title,
                            plot_bgcolor  = "rgb(243,243,243)",
                            #colorway =  "sunsetdark",
                            paper_bgcolor = "rgb(243,243,243)",
                            xaxis = dict(gridcolor = 'rgb(255, 255, 255)',title = xaxis_lab,
                                         zerolinewidth=1,ticklen=5,gridwidth=2),
                            yaxis = dict(gridcolor = 'rgb(255, 255, 255)',title = yaxis_lab,
                                         zerolinewidth=1,ticklen=5,gridwidth=2),
                           )
                      )
    return layout
    

#plot1 - mean monthly charges by tenure groups
trace1  = mean_charges("MonthlyCharges","Yes")
trace2  = mean_charges("MonthlyCharges","No")
layout1 = layout_plot("Среднемесячные расходы по группам срока пребывания",
                      "Срок пребывания в компании, месяцы","Среднемесячные расходы, доллары")
data1   = [trace1,trace2]
fig1    = go.Figure(data=data1,layout=layout1)

#plot2 - mean total charges by tenure groups
trace3  = mean_charges("TotalCharges","Yes")
trace4  = mean_charges("TotalCharges","No")
layout2 = layout_plot("Средние общие расходы по группам срока пребывания",
                      "Срок пребывания в компании, месяцы","Всего расходов, доллары")
data2   = [trace3,trace4]
fig2    = go.Figure(data=data2,layout=layout2)

py.iplot(fig1)
py.iplot(fig2)

Среднемесячные расходы по группам срока пребывания

  • У ушедших клиентов среднемесячные расходы больше, чем у оставшихся
  • С каждым месяцем расходы повышаются
  • Может влиять на уход

Средние общие расходы по группам срока пребывания

  • общие расходы у вышедших клиентов были больше, чем у оставшихся
  • Может влиять на уход
In [19]:
import plotly.figure_factory as ff
#function  for histogram for customer attrition types
def histogram(column) :
    trace1 = go.Histogram(x  = churn[column],
                          histnorm= "percent",
                          name = "Ушедшие клиенты",
                          marker = dict(color =  px.colors.sequential.Sunsetdark[3],
                              line = dict(width = .5
                                          
                                                    )
                                        ),
                          
                         opacity = .9 
                         ) 
    
    trace2 = go.Histogram(x  = not_churn[column],
                          histnorm = "percent",name = "Оставшиеся клиенты",
                          marker = dict(color =  px.colors.sequential.Sunsetdark[0],
                                        line = dict(width = .5)
                                 ),
                          opacity = .9
                         )
    
    data = [trace1,trace2]
    if column == "tenure":
        col_name = "Распределение по сроку пребывания"
        name = "Cрок пребывания"
    elif column == "MonthlyCharges":
        col_name = "Распределение по ежемесячным платежам"
        name = "Ежемесячным платеж"
    elif column == "TotalCharges":
        col_name = "Распределение по общему колличесву платежей"
        name = "Общее колличесво платежей"
    
    layout = go.Layout(dict(title =col_name ,
                            plot_bgcolor  = "rgb(243,243,243)",
                            paper_bgcolor = "rgb(243,243,243)",
                            xaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                             title = name,
                                             zerolinewidth=1,
                                             ticklen=5,
                                             gridwidth=2
                                            ),
                            yaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                             title = "Процент",
                                             zerolinewidth=1,
                                             ticklen=5,
                                             gridwidth=2
                                            ),
                           )
                      )
    fig  = go.Figure(data=data,layout=layout)
    #fig2 = ff.create_distplot([churn[column],not_churn[column]],["Churn Customers","Non churn customers"],
                             #colors =  px.colors.sequential.Sunsetdark, bin_size=[.1, .25, .5, 1], show_rug=False)

    py.iplot(fig)
    
    
    #for all categorical columns plot histogram    
for i in num_cols :
    histogram(i)

Распределение по сроку пребывания

  • Большинство клиентов уходят в первые 12-13 месяцев
  • Самое большое количество уходов из кампании происходит в 1-ый месяц (20%)
  • Большое количество клиентов остается в кампании на долгий срок: 70 - 73 месяцев (6%)
  • В остальные сроки примерно одинаоковое количество оставшихся и ушедших клиентов

Распределение по ежемесячным платежам

  • Люди, которые остались в кампании платят не очень много в месяц в основном 18-25 долларов
  • Распределение платежей людей, которые уже вышли из кампании, смещено в сторону с большей ежемесячной оплатой
  • Можно сделать предположени о том, что людей не устраивала высокая оплата, большая ежемесячная плата

Распределение по общему колличесву платежей

  • Ушедшие из кампании люди в основгом заплатили кампании меньше 1000 доларов, 30.7% клиентов заплатили всего 200 долларов
  • Были как и оставшиея так и ушедшие клиенты, которые заплатили свше 8000 долларов

Посмотрим, какими услугами польщовались клиенты с высокой оплатой:

In [20]:
df1 = telcom[(telcom.InternetService != "No") &(telcom['MonthlyCharges']>=68)]
df2 = pd.melt(df1[cols]).rename({'value': 'Пользуются услугой'}, axis=1).rename({'variable': 'Услуга'}, axis=1)
fig = px.histogram(df2, x="Услуга", color="Пользуются услугой",
                   color_discrete_sequence=[px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                  title = "Колличество людей, у которых ежемесячная оплата > 68 долларов",
                  labels = ["Количество", "Услуга"])
fig.show()

Посмотрим, какими услугами пользовались клиенты с высокой оплатой, котопые все-таки ушли из кампании:

In [21]:
df1 = telcom[(telcom.InternetService != "No") &(telcom['MonthlyCharges']>=68)&(telcom['Churn']=='Yes')]
df2 = pd.melt(df1[cols]).rename({'value': 'Пользуются услугой'}, axis=1).rename({'variable': 'Услуга'}, axis=1)
fig = px.histogram(df2, x="Услуга", color="Пользуются услугой",
                   color_discrete_sequence=[px.colors.sequential.Sunsetdark[0],px.colors.sequential.Sunsetdark[3]],
                  title = "Колличество людей, у которых ежемесячная оплата > 68 долларов, которые ушли от оператора",
                  labels = ["Количество", "Услуга"])
fig.show()

Как и для пожилых людей получилось, что самыми популярными услунами оказались телевидение и фильмы и телефон.

Вывод

Надо улучшать сервис с телевидением и фильмами.

Длительность договора

In [22]:
#cusomer attrition in tenure groups
tg_ch2  =  churn['Contract'].value_counts().reset_index()
tg_ch2.columns  = ["Contract","count"]
tg_nch2 =  not_churn['Contract'].value_counts().reset_index()
tg_nch2.columns = ["Contract","count"]

#bar - churn
trace1 = go.Bar(x = tg_ch2["Contract"]  , y = tg_ch2["count"],
                name = "Ушедшие клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[3],line = dict(width = .5)),
                opacity = .9)

#bar - not churn
trace2 = go.Bar(x = tg_nch2["Contract"] , y = tg_nch2["count"],
                name = "Оставшиеся клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[0],line = dict(width = .5)),
                opacity = .9)

layout = go.Layout(dict(title = "Отток пользователей в зависимости от длительности контракта",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                        xaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Длительность контракта",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                        yaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Количество",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                       )
                  )
data = [trace1,trace2]
fig  = go.Figure(data=data,layout=layout)
py.iplot(fig)
In [23]:
y0 = churn["tenure"]
y1 = not_churn["tenure"]

fig = go.Figure()
fig.add_trace(go.Box(y=y0, x=churn["Contract"], name='Ушли',
                marker_color = px.colors.sequential.Sunsetdark[3]))
fig.add_trace(go.Box(y=y1, x=not_churn["Contract"], name = 'Остались',
                marker_color = px.colors.sequential.Sunsetdark[0]))

fig.show()
  • Наибольший отток клиентов происходит при заключении месячного договора. Вероятно, что это связано с тем, что люди сразу не рассчитавают на долгосрочный период пребывания в кампании.

  • Контракты сроком на один и два года, вероятно, имеют договорные штрафы, и, следовательно, клиенты должны ждать до конца срока действия контракта.

Вывод

Надо лучше продвигать годовые и особенно 2-х летние контракты, чтобы больше людей преобретали их и дольше оставались в кампании.

Проверим эту гипотезу:

На сколько люди оставались после заключения каждого вида договора:

  • Н0:длительность договора не влияет на отток людей
  • Н1: длительность договора влияет на отток людей
In [24]:
f= plt.figure(figsize=(12,5))
ax=f.add_subplot(121)
sns.distplot(churn[churn['Contract']== "Month-to-month"]["tenure"],color = '#fcde9c',hist = True,ax = ax)
ax.set_title(' test')

ax=f.add_subplot(122)
sns.distplot(not_churn[not_churn['Contract']== "Month-to-month"]["tenure"],color = '#e34f6f',hist = True,ax=ax)
ax.set_title('control')
Out[24]:
Text(0.5, 1.0, 'control')

Проверим с помощью ку-ку плота, насколько распределения нормальные

In [25]:
stats.probplot(churn[churn['Contract']== "Month-to-month"]["tenure"], dist="norm", plot=plt)
plt.show()

stats.probplot(not_churn[not_churn['Contract']== "Month-to-month"]["tenure"], dist="norm", plot=plt)
plt.show()

Проверим с помощью критерия Шапиро-Уилка

In [26]:
#проверка с помощью критерия Шапиро-Уилка
groups = [churn[churn['Contract']== "Month-to-month"]["tenure"],not_churn[not_churn['Contract']== "Month-to-month"]["tenure"]]
for group in groups:
    W_value,p_value = stats.shapiro(group)
    if p_value > 0.01:
        print('Normal','W=',round(W_value,4),'p-value',round(p_value,4))
    else:
        print('Not normal','W=',round(W_value,4),'p-value',round(p_value,4))
Not normal W= 0.8016 p-value 0.0
Not normal W= 0.895 p-value 0.0

Распределение не нормальное. Используем критерий сравнения Манна-Уитни.

In [27]:
def U2 (a,b,rank_a,rank_b):
    return min((rank_a - (a.count() * (a.count() + 1))/2),(rank_b - (b.count() * (b.count() + 1))/2))
In [54]:
def MannWhitney(a,b,rank_a,rank_b):
    U = min((rank_a - (a.count() * (a.count() + 1))/2),(rank_b - (b.count() * (b.count() + 1))/2))
    m_u = (a.count()*b.count())/2
    sigma_u = np.sqrt((a.count()*b.count())*(a.count()+ b.count()+1)/12)
    z_score = (U - m_u)/sigma_u
    p_val = stats.norm.cdf(z_score) * 2
    if p_val > 0.05:
        print('Нет статистически значимой разницы и оснований отвергнуть гипотезу H0','\nU-критерий:',
              '\nz_score',z_score,'\np-value',round(p_val,4))
    else:
        print('Есть статистически значимая разница, гипотеза H0 отвергается',
              '\np-value',round(p_val,4))
        return

Из двух выборок получим одну,отсортируем и затем расставим ранги. Максимальное значение в группе имеет первый ранг и так далее.

In [40]:
telcomC = telcom[(telcom['Churn']== "Yes") & (telcom['Contract']== "Month-to-month")]
telcomCt["a"]= telcomC[['tenure']].dropna()
telcomN = telcom[(telcom['Churn']== "No") & (telcom['Contract']== "Month-to-month")]
telcomNt["b"]= telcomN[['tenure']].dropna(axis=0 )
data = pd.concat([telcomCt,telcomNt],axis=1).drop(['tenure'], axis=1)
In [41]:
df1 = data.stack().reset_index(-1).iloc[:, ::-1]
df1.columns = ['value', 'group']
df1['rank'] = df1['value'].rank(ascending = False, numeric_only = True)
In [43]:
a = df1[df1['group'] == 'a']['value']
b = df1[df1['group'] == 'b']['value']
rank_a = df1[df1['group'] == 'a']['rank'].sum()
rank_b = df1[df1['group'] == 'b']['rank'].sum()
In [ ]:
 
In [57]:
stat, p_val = stats.mannwhitneyu(a,b,alternative = 'two-sided')
if p_val > 0.05:
    print('Статистически значимой разницы нет','\np-value',round(p_val,4),'\nU-критерий',stat)
else:
    print('Разница статистически значима','\np-value',round(p_val,4))
Разница статистически значима 
p-value 0.0

Вывод

Получаем, что есть статистически значимая разница между

Интернет соединение

In [30]:
#cusomer attrition in tenure groups
tg_ch2  =  churn['InternetService'].value_counts().reset_index()
tg_ch2.columns  = ["InternetService","count"]
tg_nch2 =  not_churn['InternetService'].value_counts().reset_index()
tg_nch2.columns = ["InternetService","count"]

#bar - churn
trace1 = go.Bar(x = tg_ch2["InternetService"]  , y = tg_ch2["count"],
                name = "Ушедшие клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[3],line = dict(width = .5)),
                opacity = .9)

#bar - not churn
trace2 = go.Bar(x = tg_nch2["InternetService"] , y = tg_nch2["count"],
                name = "Оставшиеся клиенты",
                marker = dict(color =  px.colors.sequential.Sunsetdark[0],line = dict(width = .5)),
                opacity = .9)

layout = go.Layout(dict(title = "Отток пользователей в зависимости от вида интернет-соединения",
                        plot_bgcolor  = "rgb(243,243,243)",
                        paper_bgcolor = "rgb(243,243,243)",
                        xaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Вид интернет-соединения",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                        yaxis = dict(gridcolor = 'rgb(255, 255, 255)',
                                     title = "Количество",
                                     zerolinewidth=1,ticklen=5,gridwidth=2),
                       )
                  )
data = [trace1,trace2]
fig  = go.Figure(data=data,layout=layout)
py.iplot(fig)

Видно, что большинство людей, которые ушли из кампании пользовались оптоволоконным соединением.

Проверим гипотезу

Проверим гипотезу о том, что доли для разных линий связи различаются.

  • Н0: Доли никак не различаются, тип соединения не влияет на отток.

  • Н1: Доли различаются.

Критерий Хи-квадарт позволяет нам сравнить отношения количества успеха к неуспехам в двух таблицах.

In [32]:
internet = telcom[(telcom["InternetService"]!="No")][["InternetService", "Churn"]]
In [33]:
table = pd.crosstab(
    internet['InternetService'],
    internet['Churn'],
    margins = True
)
table

table1 = pd.crosstab(
    internet['InternetService'],
    internet['Churn']
)
table
Out[33]:
Churn No Yes All
InternetService
DSL 1957 459 2416
Fiber optic 1799 1297 3096
All 3756 1756 5512
In [34]:
def Chisq(table):
    expected = []
    obs1 = np.append(table.iloc[0][0:2].values, table.iloc[1][0:2].values)
    rows = table.iloc[0:2,2].values
    cols = table.iloc[2,0:2].values
    total = table.loc['All','All']
    for count in range(2):
        for column in cols:
            expected.append((column*rows[count])/total)
    obs = obs1 + 0.5 * np.sign(expected - obs1)
    return sum(((obs - expected)**2)/expected)
In [35]:
Chisq(table)
Out[35]:
326.6008511107608
In [36]:
p_value = 1 - stats.chi2.cdf(Chisq(table),1)

if p_value > 0.05:
    print('Не можем отвергнуть нулевую гипотезу о том, что доли не имеют значимого различия', '\nХи-квадрат критерий=',Chisq(table),'\np-value = ',p_value)
else:
    print('Не можем принять нулевую гипотезу о том, что доли не имеют значимого различия','\nХи-квадрат критерий=',Chisq(table),'\np-value = ',p_value)
Не можем принять нулевую гипотезу о том, что доли не имеют значимого различия 
Хи-квадрат критерий= 326.6008511107608 
p-value =  0.0

Вывод

Не можем принять нулевую гипотезу о том, что доли не имеют значимого различия.

Тип соединения влияет на отток.

Выводы

  • Таким образом, мы получили, что нужно больше поддрерживать сервисы для просмотра телевизионных программ и фильмов, потому что они являются очень популярными услугами среди всех пользователей, у которых подключен интернет.
  • Нужно также улучшить сервис, с помощью которого происходит оплата по электронному чеку, потому что, возможно, там есть какие-то проблемы и поэтому люди уходят.
  • Маркетологам нужно продвигать договоры на более долгий период: на год или два, потому что люди с таким типом договора дольше остаются в кампании и отток пользователей мал по сравнению с ежемесячной оплатой.
  • Оптоволоконное соединение также не устраивает клиентов и происходит большой отток. Надо попытаться улучшить и его.
In [ ]: